﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace NanoGedcom
{
    /// <summary>
    /// Static Tools of GedcomParser
    /// </summary>
    public static class GedcomParser
    {
        private class Node(string rawLine)
        {
            public string RawLine { get; } = rawLine;
            public List<Node> Childs { get; } = new List<Node>();


            internal string Tag
            {
                get
                {
                    var end = RawLine.IndexOf(" ", 2);
                    if (end < 0)
                        return RawLine.Substring(2); // No value after
                    return RawLine.Substring(2, end - 2);
                }
            }

            internal bool HasValue
            {
                get
                {
                    return RawLine.Length > (Tag.Length + 2);
                }
            }

            internal string GetTextValue()
            {
                return RawLine.Substring(3 + Tag.Length).Trim();
            }

            internal int? GetInt32Value()
            {
                if (int.TryParse(GetTextValue(), out var value))
                    return value;
                else
                    return null;
            }
        }

        /// <summary>
        /// Load and parse a Gedcom file.
        /// </summary>
        /// <param name="file">Path to the file</param>
        /// <param name="encoding">encoding of file. If null, parser use UTF-8</param>
        /// <returns></returns>
        public static Gedcom Load(string file, Encoding encoding = null)
        {
            if (encoding == null)
                encoding = Encoding.UTF8;
            var lines = File.ReadAllLines(file, encoding);

            var root = new List<Node>();
            var childsLists = new List<Node>[11];

            childsLists[0] = root;

            foreach (var line in lines)
            {
                int newlevel;
                if (line.Length > 2 && line[0] >= '0' && line[0] <= '9' && line[1] == ' ')
                {
                    newlevel = (line[0] - '0');
                }
                else
                    throw new Exception("Unable to parse level");

                var newNode = new Node(line.Trim());

                childsLists[newlevel].Add(newNode);
                childsLists[newlevel + 1] = newNode.Childs;
            }

            //The tree is done 

            var tasks = new List<Task>();
            var gedcom = new Gedcom();
            if (root.Count < 2)
                throw new Exception("File must contain Header and Trailer");

            //Get header
            var node = root[0];
            if (!node.RawLine.Equals("0 HEAD"))
                throw new Exception("Header not found");
            tasks.Add(new Task(() => { ParseHeader(gedcom, gedcom.Header, node.Childs); }));
            root.Remove(node);

            //Get trailer
            node = root[root.Count - 1];
            if (!node.RawLine.Equals("0 TRLR"))
                throw new Exception("Trailer not found");
            root.Remove(node);

            //all other have an Id
            foreach (var levelZeroNode in root)
            {
                //Must be always 3 parameter 0 / Id / Type

                //Get Id
                var position = levelZeroNode.RawLine.IndexOf(' ', 2);
                if (position < 0)
                    throw new Exception("Root element malformed");
                var id = levelZeroNode.RawLine.Substring(2, position - 2);

                //Get Tag                
                var tag = levelZeroNode.RawLine.Substring(3 + id.Length);


                if (tag.Equals("INDI"))
                {
                    var individu = new Individu(id);
                    tasks.Add(new Task(() => { ParseIndividu(gedcom, individu, levelZeroNode.Childs); }));
                    gedcom.Individus.Add(individu);
                }
                else if (tag.Equals("FAM"))
                {
                    var family = new Family(id);
                    tasks.Add(new Task(() => { ParseFamily(gedcom, family, levelZeroNode.Childs); }));
                    gedcom.Families.Add(family);
                }
                else if (tag.Equals("SOUR"))
                {
                    var source = new Source(id);
                    tasks.Add(new Task(() => { ParseSource(gedcom, source, levelZeroNode.Childs); }));
                    gedcom.Sources.Add(source);
                }
                else if (tag.Equals("SUBM"))
                {
                    var submiter = new Submiter(id);
                    tasks.Add(new Task(() => { ParseSubmiter(gedcom, submiter, levelZeroNode.Childs); }));
                    gedcom.Submiters.Add(submiter);
                }
                else if (tag.Equals("REPO"))
                {
                    var repository = new Repository(id);
                    tasks.Add(new Task(() => { ParseRepository(gedcom, repository, levelZeroNode.Childs); }));
                    gedcom.Repositories.Add(repository);
                }
                else if (tag.Equals("NOTE"))
                {
                    var note = new Note(id);
                    tasks.Add(new Task(() => { ParseNote(gedcom, note, levelZeroNode.Childs); }));
                    gedcom.Notes.Add(note);
                }
                else
                {
                    //Console.WriteLine(tag);
                }
            }


            //Run all tasks
            foreach (var task in tasks)
            {
                task.Start();
            }

            //Wait all thread
            Task.WaitAll(tasks.ToArray());

            return gedcom;
        }

        #region Individu
        private static void ParseIndividu(Gedcom gedcom, Individu individu, List<Node> childs)
        {
            //Console.WriteLine("ParseIndividu {0}", individu.Id);

            foreach (var child in childs)
            {
                switch (child.Tag)
                {
                    case "NAME":
                        if (child.HasValue)
                        {
                            var name = new IndividualName();
                            name.Full = child.GetTextValue();
                            ParseName(gedcom, name, child.Childs);
                            individu.Names.Add(name);
                        }
                        break;
                    case "SEX":
                        if (child.HasValue)
                        {
                            individu.Sexe = child.GetTextValue()[0];
                        }
                        break;
                    case "DEAT":
                    case "BIRT":
                        var individualEvent = new Event();
                        individualEvent.Type = child.Tag;
                        ParseEvent(gedcom, individualEvent, child.Childs);
                        individu.Events.Add(individualEvent);
                        break;
                    case "OCCU":
                        if (!child.HasValue)
                            break;
                        var individualAttribute = new IndividualAttribute();
                        individualAttribute.Text = child.GetTextValue();
                        individualAttribute.Type = child.Tag;
                        ParseAttribute(gedcom, individualAttribute, child.Childs);
                        individu.Attributes.Add(individualAttribute);
                        break;
                    /*case "ADOP" :
                        Console.WriteLine(child.RawLine);
                        foreach (var ch in child.Childs) {
                            Console.WriteLine("\t{0}",ch.RawLine);
                        }
                        break; */
                    case "FAMC":
                        if (child.HasValue)
                        {
                            var family = gedcom.GetFamily(child.GetTextValue());
                            if (family != null)
                                individu.ChildInFamilies.Add(family);
                        }
                        break;
                    case "FAMS":
                        if (child.HasValue)
                        {
                            var family = gedcom.GetFamily(child.GetTextValue());
                            if (family != null)
                                individu.ParentInFamilies.Add(family);
                        }
                        break;//NMR
                    case "NCHI":
                        if (child.HasValue)
                            individu.KnowChilds = child.GetInt32Value();

                        break;
                    case "NMR":
                        if (child.HasValue)
                            individu.KnowMarriages = child.GetInt32Value();

                        break;
                }

            }







        }
        #endregion

        #region Familly
        private static void ParseFamily(Gedcom gedcom, Family family, List<Node> childs)
        {
            foreach (var child in childs)
            {
                switch (child.Tag)
                {
                    case "HUSB":
                        if (child.HasValue)
                        {
                            var husband = gedcom.GetIndividu(child.GetTextValue());
                            if (husband != null)
                                family.Husband = husband;
                        }
                        break;
                    case "WIFE":
                        if (child.HasValue)
                        {
                            var wife = gedcom.GetIndividu(child.GetTextValue());
                            if (wife != null)
                                family.Wife = wife;

                        }
                        break;
                    case "CHIL":
                        if (child.HasValue)
                        {
                            var children = gedcom.GetIndividu(child.GetTextValue());
                            if (children != null)
                                family.Childs.Add(children);

                        }
                        break;
                    case "NCHI":
                        if (child.HasValue)
                            family.KnowChilds = child.GetInt32Value();

                        break;
                }


            }
        }
        #endregion

        #region Source
        private static void ParseSource(Gedcom gedcom, Source source, List<Node> childs)
        {
            //Console.WriteLine("ParseSource {0}", source.Id);
        }

        #endregion

        #region Repository
        private static void ParseRepository(Gedcom gedcom, Repository repository, List<Node> childs)
        {
            //Console.WriteLine("ParseRepository {0}", repository.Id);
        }
        #endregion

        #region Submiter
        private static void ParseSubmiter(Gedcom gedcom, Submiter submiter, List<Node> childs)
        {
            //Console.WriteLine("ParseSubmiter {0}", submiter.Id);
        }
        #endregion

        #region Note
        private static void ParseNote(Gedcom gedcom, Note note, List<Node> childs)
        {
            //Console.WriteLine("ParseNote {0}", note.Id);
        }
        #endregion

        #region Header
        private static void ParseHeader(Gedcom gedcom, Header header, List<Node> childs)
        {
            //Console.WriteLine("ParseHeader");
        }
        #endregion

        #region Event
        // Could have link to Source don't remove parameter gedcom
        private static void ParseEvent(Gedcom gedcom, Event myEvent, List<Node> childs)
        {

            foreach (var child in childs)
            {
                switch (child.Tag)
                {
                    case "DATE":
                        if (child.HasValue)
                        {
                            myEvent.Date = child.GetTextValue();


                        }
                        break;
                    case "PLAC":
                        if (child.HasValue)
                        {
                            myEvent.Location = child.GetTextValue();
                        }
                        break;

                }
            }

        }
        #endregion

        #region Attribute
        // Could have link to Source don't remove parameter gedcom
        private static void ParseAttribute(Gedcom gedcom, IndividualAttribute myEvent, List<Node> childs)
        {

            foreach (var child in childs)
            {
                switch (child.Tag)
                {
                    case "DATE":
                        if (child.HasValue)
                        {
                            myEvent.Date = child.GetTextValue();


                        }
                        break;
                    case "PLAC":
                        if (child.HasValue)
                        {
                            myEvent.Location = child.GetTextValue();
                        }
                        break;

                }
            }

        }
        #endregion

        #region Name
        private static void ParseName(Gedcom gedcom, IndividualName name, List<Node> childs)
        {
            foreach (var child in childs)
            {
                switch (child.Tag)
                {
                    case "TYPE":
                        if (child.HasValue)
                            name.Type = child.GetTextValue();
                        break;
                    case "NPFX":
                        if (child.HasValue)
                            name.Prefix = child.GetTextValue();
                        break;
                    case "GIVN":
                        if (child.HasValue)
                            name.Given = child.GetTextValue();
                        break;
                    case "NICK":
                        if (child.HasValue)
                            name.Nickname = child.GetTextValue();
                        break;
                    case "SPFX":
                        if (child.HasValue)
                            name.SurnamePrefix = child.GetTextValue();
                        break;
                    case "SURN":
                        if (child.HasValue)
                            name.Surname = child.GetTextValue();
                        break;
                    case "NSFX":
                        if (child.HasValue)
                            name.Sufix = child.GetTextValue();
                        break;
                }
            }
        }
        #endregion
    }



}
